#!/bin/sh
set -e

# OpenEuler Embedded K3s Agent Deploy Script
# Modified from k3s-io install.sh
#
# Usage:
#   curl ... | ENV_VAR=... sh -
#       or
#   ENV_VAR=... ./install.sh
#
# **Only if you know what you are doing, you can use this script to install k3s agent on OpenEuler Embedded.
#
# What is hardcoded:
#   INSTALL_K3S_CHANNEL=stable
#   INSTALL_K3S_SKIP_SELINUX_RPM=true
#   INSTALL_K3S_TYPE=exec (not notify for server)
#   INSTALL_K3S_EXEC="agent "
# Example:
#   Installing an agent with airgap images:
#     AIRGAP_IMAGES_DIR=/path/to/images K3S_URL=https://server-url:6443 K3S_TOKEN=xxx k3s-install
#   Installing an agent with custom container runtime:
#     K3S_URL=https://server-url:6443 K3S_TOKEN=xxx CONTAINER_RT_EP=unix:///custom/path.sock k3s-install
#
# Environment variables:
#   - K3S_URL (REQUIRED)
#     The URL of the k3s server to connect to in agent mode. Must use HTTPS protocol.
#
#   - K3S_TOKEN/K3S_TOKEN_FILE (REQUIRED)
#     Cluster secret token for agent authentication. At least one must be set.
#     CLUSTER_SECRETE is deprecated
#
#   - AIRGAP_IMAGES_DIR
#     Directory containing k3s-airgap-images-ARCH.tar.gz for offline installation
#
#   - AIRGAP_IMAGES
#     Directly specify k3s-airgap images for offline installation
#
#   - ARGS
#     ARGS will be after `k3s agent`, e.g.: 
#       ARGS="--disable=traefik" => "k3s agent --disable=traefik"
#
#   - CONTAINER_RT_EP
#     Container runtime endpoint type 
#     default CONTAINER_RT_EP=isulad => --container-runtime-endpoint unix:///var/run/isulad.sock instead of containerd.sock)
#     if CONTAINER_RT_EP=embedded => use embedded k3s containerd instead.
#     otherwise => --container-runtime=endpint "${CONTAINER_RT_EP}"
#
#   - INSTALL_K3S_BIN_DIR
#     Directory to install k3s binary and scripts (default: /usr/bin)
#
#   - INSTALL_K3S_SYSTEMD_DIR
#     Directory to install systemd service files (default: /etc/systemd/system)
#
#   - INSTALL_K3S_BIN_DIR_READ_ONLY
#     If set to true, prevents writing to INSTALL_K3S_BIN_DIR
#
#   - SKIP_LOAD_AIRGAP
#     If set to true, prevents loading airgap images to isula
#
#   - ONLY_SET_ISULAD
#     If set to true, only isulad configuration will take place
#
# Notes:
# 1. This version ONLY supports agent mode and requires:
#    - systemd as process supervisor (SysV is in progress)
#    - isulad as container runtime endpoint
#    - Preloaded pause image (docker.io/rancher/mirrored-pause:3.6)
# 2. Automatically configures isulad:
#    - Configures cni-conf-dir and cni-bin-dir for k3s
# 3. Airgap installation requires:
#    - k3s-airgap-images-ARCH.tar.gz in AIRGAP_IMAGES_DIR
#    - Properly configured container registry
#
# For detailed documentation about k3s, see: https://docs.k3s.io/

# --- helper functions for logs ---
info() {
    echo '[INFO] ' "$@"
}
warn()
{
    echo '[WARN] ' "$@" >&2
}
fatal() {
    echo '[ERROR] ' "$@" >&2
    exit 1
}

# --- fatal if not system (future: or SysV) ---
verify_system() {
    if [ -x /sbin/systemd ]; then
        HAS_SYSTEMD=true
        return 
    fi
    fatal 'Can not find systemd to use as a process supervisor for k3s'
}

# --- add quotes to command arguments ---
quote() {
    for arg in "$@"; do
        printf '%s\n' "$arg" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/'/"
    done
}

# --- add indentation and trailing slash to quoted args ---
quote_indent() {
    printf ' \\\n'
    for arg in "$@"; do
        printf '\t%s \\\n' "$(quote "$arg")"
    done
}

# --- escape most punctuation characters, except quotes, forward slash, and space ---
escape() {
    printf '%s' "$@" | sed -e 's/\([][!#$%&()*;<=>?\_`{|}]\)/\\\1/g;'
}

# --- escape double quotes ---
escape_dq() {
    printf '%s' "$@" | sed -e 's/"/\\"/g'
}

# --- ensures $K3S_URL is empty or begins with https://, exiting fatally otherwise ---
verify_k3s_url() {
    case "${K3S_URL}" in
        "")
          fatal "missing K3S_URL"
            ;;
        https://*)
            ;;
        *)
            fatal "Only https:// URLs are supported for K3S_URL (have ${K3S_URL})"
            ;;
    esac
}

# --- setup basic environment ---
setup_env() {

    # always set OEE as agent mode
    AIRGAP_DIR="${AIRGAP_IMAGES_DIR:-/etc/k3s/tools}"
    rawARCH="$(uname -m)"
    case "$rawARCH" in
        arm)
            ARCH=arm;;
        aarch64|arm64) 
            ARCH=arm64 ;;
        x86_64)
            ARCH=amd64 ;;
        s390x)
            ARCH=s390x ;;
        *)
            fatal "unsupported architecture ${rawARCH}"
            ;;
    esac

    info  "got arch=${ARCH}"

    if [ ! -n "${AIRGAP_IMAGES}" ]; then
      AIRGAP_IMAGES="${AIRGAP_DIR}/k3s-airgap-images-${ARCH}.tar.gz"
    fi
     

    
    if [ "$ONLY_SET_ISULAD" = "true" ]; then
      config_isulad 
      exit 0
    fi
    verify_k3s_url

    CONTAINER_RT_EP=${CONTAINER_RT_EP:-"isulad"}
    case $CONTAINER_RT_EP in
      "isulad")
        CONTAINER_RUNTIME_ENDPOINT="--container-runtime-endpoint=unix:///var/run/isulad.sock"
        ;;
      "embedded")
        CONTAINER_RUNTIME_ENDPOINT=""
        ;;
      *) 
        CONTAINER_RUNTIME_ENDPOINT="--container-runtime-endpoint=${CONTAINER_RT_EP}"
        ;;
    esac

    CMD_K3S=agent
    CMD_K3S_EXEC="${CMD_K3S}$(quote_indent ""$@""${CONTAINER_RUNTIME_ENDPOINT}"")"
    SYSTEM_NAME=k3s-${CMD_K3S}

    SUDO=sudo
    if [ $(id -u) -eq 0 ]; then
        SUDO=
    fi

    SYSTEM_TYPE=exec

    # --- set binary and service directories
    BIN_DIR=${INSTALL_K3S_BIN_DIR:-/usr/bin}
    if ! $SUDO sh -c "touch ${BIN_DIR}/k3s-ro-test && rm -rf ${BIN_DIR}/k3s-ro-test"; then
        if [ -d /opt/bin ]; then
            BIN_DIR=/opt/bin
        fi
    fi

    # --- use systemd directory if defined or create default ---
    if [ -n "${INSTALL_K3S_SYSTEMD_DIR}" ]; then
        SYSTEMD_DIR="${INSTALL_K3S_SYSTEMD_DIR}"
    else
        SYSTEMD_DIR=/etc/systemd/system
    fi
    
    # --- set service and environment file paths
    SERVICE_K3S=${SYSTEM_NAME}.service
    UNINSTALL_K3S_SH=${UNINSTALL_K3S_SH:-${BIN_DIR}/${SYSTEM_NAME}-uninstall.sh}
    KILLALL_K3S_SH=${KILLALL_K3S_SH:-${BIN_DIR}/k3s-killall.sh}
    AGENT_KILLER=${KILL_AGENT:-${BIN_DIR}/k3s-kill-agent}

    FILE_K3S_SERVICE=${SYSTEMD_DIR}/${SERVICE_K3S}
    FILE_K3S_ENV=${SYSTEMD_DIR}/${SERVICE_K3S}.env

}

# --- create killall script ---
create_agent_killer() {
    [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return
    info "Creating killall script ${AGENT_KILLER}"
    $SUDO tee ${AGENT_KILLER} >/dev/null << \EOF
#!/bin/sh -eu
#
# Copyright (C) 2020 Axis Communications AB
#
# SPDX-License-Identifier: Apache-2.0

do_unmount() {
  [ $# -eq 2 ] || return
  local mounts=
  while read ignore mount ignore; do
    case $mount in
      $1/*|$2/*)
        mounts="$mount $mounts"
        ;;
    esac
  done </proc/self/mounts
  [ -z "$mounts" ] || umount $mounts
}

do_unmount /run/k3s /var/lib/rancher/k3s

# The lines below come from install.sh's create_killall() function:
ip link show 2>/dev/null | grep 'master cni0' | while read ignore iface ignore; do
    iface=${iface%%@*}
    [ -z "$iface" ] || ip link delete $iface
done

ip link delete cni0
ip link delete flannel.1
rm -rf /var/lib/cni/
EOF
    $SUDO chmod 755 ${AGENT_KILLER}
    $SUDO chown root:root ${AGENT_KILLER}
}


# --- create killall script ---
create_killall() {
    [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return
    info "Creating k3s agent cleaner  ${KILLALL_K3S_SH}"
    $SUDO tee ${KILLALL_K3S_SH} >/dev/null << \EOF
#!/bin/sh
[ $(id -u) -eq 0 ] || exec sudo $0 $@

for bin in /var/lib/rancher/k3s/data/**/bin/; do
    [ -d $bin ] && export PATH=$PATH:$bin:$bin/aux
done

set -x

for service in /etc/systemd/system/k3s*.service; do
    [ -s $service ] && systemctl stop $(basename $service)
done

for service in /etc/init.d/k3s*; do
    [ -x $service ] && $service stop
done

pschildren() {
    ps -e -o ppid= -o pid= | \
    sed -e 's/^\s*//g; s/\s\s*/\t/g;' | \
    grep -w "^$1" | \
    cut -f2
}

pstree() {
    for pid in $@; do
        echo $pid
        for child in $(pschildren $pid); do
            pstree $child
        done
    done
}

killtree() {
    kill -9 $(
        { set +x; } 2>/dev/null;
        pstree $@;
        set -x;
    ) 2>/dev/null
}

getshims() {
    ps -e -o pid= -o args= | sed -e 's/^ *//; s/\s\s*/\t/;' | grep -w 'k3s/data/[^/]*/bin/containerd-shim' | cut -f1
}

killtree $({ set +x; } 2>/dev/null; getshims; set -x)

do_unmount_and_remove() {
    awk -v path="$1" '$2 ~ ("^" path) { print $2 }' /proc/self/mounts | sort -r | xargs -r -t -n 1 sh -c 'umount "$0" && rm -rf "$0"'
}

do_unmount_and_remove '/run/k3s'
do_unmount_and_remove '/var/lib/rancher/k3s'
do_unmount_and_remove '/var/lib/kubelet/pods'
do_unmount_and_remove '/run/netns/cni-'

# Remove CNI namespaces
ip netns show 2>/dev/null | grep cni- | xargs -r -t -n 1 ip netns delete

# Delete network interface(s) that match 'master cni0'
ip link show 2>/dev/null | grep 'master cni0' | while read ignore iface ignore; do
    iface=${iface%%@*}
    [ -z "$iface" ] || ip link delete $iface
done
ip link delete cni0
ip link delete flannel.1
rm -rf /var/lib/cni/
iptables-save | grep -v KUBE- | grep -v CNI- | iptables-restore
EOF
    $SUDO chmod 755 ${KILLALL_K3S_SH}
    $SUDO chown root:root ${KILLALL_K3S_SH}
}

# --- create uninstall script ---
create_uninstall() {
    [ "${INSTALL_K3S_BIN_DIR_READ_ONLY}" = true ] && return
    info "Creating uninstall script ${UNINSTALL_K3S_SH}"
    $SUDO tee ${UNINSTALL_K3S_SH} >/dev/null << EOF
#!/bin/sh
set -x
[ \$(id -u) -eq 0 ] || exec sudo \$0 \$@

${KILLALL_K3S_SH}

if which systemctl; then
    systemctl disable ${SYSTEM_NAME}
    systemctl reset-failed ${SYSTEM_NAME}
    systemctl daemon-reload
fi
if which rc-update; then
    rc-update delete ${SYSTEM_NAME} default
fi

rm -f ${FILE_K3S_SERVICE}
rm -f ${FILE_K3S_ENV}

remove_uninstall() {
    rm -f ${UNINSTALL_K3S_SH}
}
trap remove_uninstall EXIT

if (ls ${SYSTEMD_DIR}/k3s*.service || ls /etc/init.d/k3s*) >/dev/null 2>&1; then
    set +x; echo 'Additional k3s services installed, skipping uninstall of k3s'; set -x
    exit
fi

for cmd in kubectl crictl ctr; do
    if [ -L ${BIN_DIR}/\$cmd ]; then
        rm -f ${BIN_DIR}/\$cmd
    fi
done

rm -rf /etc/rancher/k3s
rm -rf /run/k3s
rm -rf /run/flannel
rm -rf /var/lib/rancher/k3s
rm -rf /var/lib/kubelet
rm -f ${BIN_DIR}/k3s
rm -f ${KILLALL_K3S_SH}

if type yum >/dev/null 2>&1; then
    yum remove -y k3s-selinux
    rm -f /etc/yum.repos.d/rancher-k3s-common*.repo
fi
EOF
    $SUDO chmod 755 ${UNINSTALL_K3S_SH}
    $SUDO chown root:root ${UNINSTALL_K3S_SH}
}

create_scripts() {
    create_agent_killer
    create_uninstall
}

systemd_disable() {
    $SUDO systemctl disable ${SYSTEM_NAME} >/dev/null 2>&1 || true
    $SUDO rm -f /etc/systemd/system/${SERVICE_K3S} || true
    $SUDO rm -f /etc/systemd/system/${SERVICE_K3S}.env || true
}

# --- create environment file ---
create_env_file() {
    info "Creating environment file ${FILE_K3S_ENV}"
    UMASK=$(umask)
    umask 0377
    if [ -z "${K3S_TOKEN}" ]; then
      fatal "missing K3S_TOKEN."
      exit -1
    fi
    env | grep '^K3S_' | $SUDO tee ${FILE_K3S_ENV} >/dev/null
    env | grep -E '^(NO|HTTP|HTTPS)_PROXY' | $SUDO tee -a ${FILE_K3S_ENV} >/dev/null
    umask $UMASK
}

# --- verify an executable k3s binary is installed ---
verify_k3s_is_executable() {
    if [ ! -x ${BIN_DIR}/k3s ]; then
        fatal "Executable k3s binary not found at ${BIN_DIR}/k3s"
    fi
}

# --- create systemd service file ---
create_systemd_service_file() {
    info "Creating service file ${FILE_K3S_SERVICE}"
    $SUDO tee ${FILE_K3S_SERVICE} >/dev/null << EOF
[Unit]
Description=Lightweight Kubernetes
Documentation=https://k3s.io
Wants=network-online.target
After=network-online.target

[Install]
WantedBy=multi-user.target

[Service]
Type=exec
EnvironmentFile=${FILE_K3S_ENV}
KillMode=process
Delegate=yes
LimitNOFILE=1048576
LimitNPROC=infinity
LimitCORE=infinity
TasksMax=infinity
TimeoutStartSec=0
Restart=always
RestartSec=5s
ExecStart=${BIN_DIR}/k3s \\
    ${CMD_K3S_EXEC}

EOF
}


# --- enable and start systemd service ---
systemd_enable_and_start() {
    info "Enabling ${SYSTEM_NAME} unit"
    $SUDO systemctl enable ${FILE_K3S_SERVICE} >/dev/null
    $SUDO systemctl daemon-reload >/dev/null
    
    info "Starting ${SYSTEM_NAME}"
    $SUDO systemctl restart ${SYSTEM_NAME}
}


isulad_daemon_set() {
    sed -i 's/"cni-bin-dir": "*",/"cni-bin-dir": "\/var\/lib\/rancher\/k3s\/data\/current\/bin",/' /etc/isulad/daemon.json
    sed -i 's/"cni-conf-dir": "*",/"cni-conf-dir": "\/var\/lib\/rancher\/k3s\/agent\/etc\/cni\/net.d",/' /etc/isulad/daemon.json
    sed -i 's/"pod-sandbox-image": "*",/"pod-sandbox-image": "docker.io\/rancher\/mirrored-pause:3.6",/' /etc/isulad/daemon.json
    systemctl daemon-reload
    systemctl restart isulad
}

isulad_preload_images() {
    if [ "${SKIP_LOAD_AIRGAP}" = true ]; then
      info "skipped airgap images loading"
      return 0
    fi
    info "searching for : ${AIRGAP_IMAGES}"
    if ! test -f "${AIRGAP_IMAGES}"; then
      fatal "Unable to find ${AIRGAP_IMAGES} tarball"
    fi  
    isula load -i "${AIRGAP_IMAGES}" > /dev/null
}

config_isulad() {
  isulad_daemon_set 
  isulad_preload_images
}

eval set -- $(escape "${ARGS}") $(quote "${@}")

{
    setup_env "$@"
    create_scripts
    create_env_file
    if [ "${CONTAINER_RT_EP}" = "isulad" ]; then
      config_isulad
    elif [ "${CONTAINER_RT_EP}" = "embedded" ]; then
      create_systemd_service_file 
    else
      echo "customize k3s-agent.service manually for endpoint ${CONTAINER_RT_EP}"
    fi
    systemd_enable_and_start
}

